const animAction = 'Move'
let spinePosition =  {
  right: 200,
  bottom: 400,
  top: 0,
  left: 0
};



let canvas;
let gl;
let shader;
let batcher
let mvp = new spine.webgl.Matrix4()
let assetManager
let skeletonRenderer
let lastFrameTime;
let startFrameTime
let spineboy;
let animationFrameId;
let ar
let animation
let tasks = []
let duration
let animationsNames = []
let ququeIndex = 1
let scale = 1
let runTasksTime

let extensionOption = {
  spineList: [],
  scaleXGlobal: -1,
  scaleMult: 0.3
}
let isSetting = false
let settingOption = null

let o_canvas_spine = document.createElement('canvas');
let o_spine_position = document.createElement('div');
let osp_position_setting = document.createElement('div')
o_canvas_spine.setAttribute('id', 'o-canvas-spine');
o_canvas_spine.setAttribute('class', 'o-spine');

o_spine_position.setAttribute('id', 'o-spine-position');
o_spine_position.setAttribute('class', 'o-spine-position');
osp_position_setting.setAttribute('class', 'osp-setting-postion')
osp_position_setting.innerHTML = `
  <p>设定初始位置中，拉动动线范围调整到想要的位置上...</p>
  <div class="ops-position-button-bar">
    <button class="osp-button" id="ops-setting-submit" style="margin-right: 10px;">确定</button>
    <button class="osp-button osp-button-cancel" id="ops-setting-cancel">取消</button>
  </div>
`
o_spine_position.appendChild(osp_position_setting)
// o_spine_position.setAttribute('style', 'border: 1px solid red;');
document.body.appendChild(o_canvas_spine);
document.body.appendChild(o_spine_position);


const spinePositionEl = document.getElementById('o-spine-position')
const opsSettingSubmit = document.getElementById('ops-setting-submit')
const opsSetingCancel = document.getElementById('ops-setting-cancel')

let bonesPositionYMax = 0
let bonesPositionXMax = 0
let bonesLeft = 0
let bonesTop = 0
spinePositionEl.style.height = bonesPositionYMax+'px'
spinePositionEl.style.width = bonesPositionXMax+'px'
spinePositionEl.style.left = bonesLeft+'px'
spinePositionEl.style.top = bonesTop+'px'

// 构造函数？
const injectOption = (option) => {
  extensionOption = {...extensionOption, ...option}
  
  // let copySpines = JSON.parse(spines)
  for (const item of extensionOption.spineList) {
    item.skelPath = chrome.runtime.getURL(item.skelPath);
    item.atlasPath = chrome.runtime.getURL(item.atlasPath);
  }
  // spineList = spines
}

const init = () => {
  canvas = document.getElementById('o-canvas-spine')
  canvas.width = window.innerWidth
  canvas.height = window.innerHeight
  const config = { 
    alpha: true,
    viewport: {
      transitionTime: 1 // Transition between viewports for 1 second
    },
    powerPreference: "high-performance",
    preserveDrawingBuffer: true,
    antialias: true
  }
  
  gl =
    canvas.getContext('webgl', config) ||
    canvas.getContext('experimental-webgl', config)
  if (!gl) {
    alert('WebGL is unavailable.')
    return
  }
  shader = spine.webgl.Shader.newTwoColoredTextured(gl)
  batcher = new spine.webgl.PolygonBatcher(gl)
  mvp.ortho2d(0, 0, canvas.width - 1, canvas.height - 1)
  skeletonRenderer = new spine.webgl.SkeletonRenderer(gl)
  assetManager = new spine.webgl.AssetManager(gl)
  for (const item of extensionOption.spineList) {
    assetManager.loadBinary(item.skelPath)
    assetManager.loadTextureAtlas(item.atlasPath)
  }
  
  animationFrameId = requestAnimationFrame(load)
}

const load = () => {
  if (assetManager.isLoadingComplete()) {
    for (const item of extensionOption.spineList) {
      item.spineboy = loadSpineboy(animAction, true, item)
      redirectSpine(item)
      if (item.isIndex) {
        animationsNames = item.spineboy.skeleton.data.animations.map((p) => p.name)
        spineboy = item.spineboy
        calcSpinePosition(item)
        setupAnimations()
      }
        
      else
        item.spineboy.state.setAnimation(0, animAction, true)
      
    }
      // spineboy = loadSpineboy(animAction, true, path)
      // animationsNames = spineboy.skeleton.data.animations.map((p) => p.name)
      // 计算小人的位置和大小
      // redirectSpine()
      // calcSpinePosition()

      // setupAnimations(0)
      lastFrameTime = Date.now() / 1000
      animationFrameId = requestAnimationFrame(render) // Loading is done, call render every frame.
    } else {
      animationFrameId = requestAnimationFrame(load)
    }
}

const loadSpineboy = (initialAnimation, premultipliedAlpha, spineItem) => {
  const atlas = assetManager.get(spineItem.atlasPath)
  const atlasLoader = new spine.AtlasAttachmentLoader(atlas)
  const skeletonBinary = new spine.SkeletonBinary(atlasLoader)
  skeletonBinary.scale = 1
  const skeletonData = skeletonBinary.readSkeletonData(
    assetManager.get(spineItem.skelPath)
  )
  const skeleton = new spine.Skeleton(skeletonData)
  const bounds = calculateSetupPoseBounds(skeleton)
  const animationStateData = new spine.AnimationStateData(skeleton.data)
  const animationState = new spine.AnimationState(animationStateData)
  skeleton.scaleX = extensionOption.scaleXGlobal
  return {
    skeleton: skeleton,
    state: animationState,
    bounds: bounds,
    premultipliedAlpha: premultipliedAlpha,
  }
}

// 处理小人行动逻辑
const setupAnimations = (sign = 0) => {
  // 标识符sign,1:触摸反馈，2：到达左边边界，3：到达右边边界
  // 随机动作
  // let animationsNames = spineItem.animationsNames
  if (!animationsNames || !animationsNames.length) return
  let actionIndex = Math.floor(Math.random() * animationsNames.length)
  let action = animationsNames[actionIndex]
  // console.log(action)
  if (sign !== 1 && (action === 'Default' || action === 'Interact')) {
    setupAnimations()
    return
  }
  
  // 随机时间倍率 1-4倍，特殊动作只要一倍
  let actionMult = Math.floor(Math.random() * 4) + 1
  let isMove = false
  let scaleXRandom = Math.floor(Math.random() * 2)
  if (action === 'Special') actionMult = 1
  if (action === 'Move') isMove = true
  switch (sign) {
    case 1:
      action = 'Interact'
      actionMult = 1
      isMove = false
      break;
    case 2:
      action = 'Move'
      isMove = true
      scaleXRandom = 1
      actionMult = 1
      break;
    case 3:
      action = 'Move'
      isMove = true
      scaleXRandom = 0
      actionMult = 1
      break;
    default:
      break;
  }
  
  // addAnimation(未知，动画名，是否循环，上一个动画持续的时间)
  if (ququeIndex===1) {
    animation = spineboy.state.setAnimation(0, action, true)
  }
  else {
    animation = spineboy.state.addAnimation(0, action, true, duration)
  }
  if (animation.animationEnd >= 8) {
    duration = animation.animationEnd/2*actionMult
  }
  else if (animation.animationEnd <= 2) {
    duration = animation.animationEnd*2*actionMult
  }
  else {
    duration = animation.animationEnd*actionMult
  }
  schedule(ququeIndex, duration,{
    animation: animation,
    duration: duration,
    isMove: isMove,
    scaleX: scaleXRandom?1:-1
  })
  ququeIndex++
  if (ququeIndex === 6) {
    ququeIndex =1
    runTasks()
    return
  }
  setupAnimations()
}

const calculateSetupPoseBounds = (skeleton) => {
  skeleton.setToSetupPose()
  skeleton.updateWorldTransform()
  const offset = new spine.Vector2()
  const size = new spine.Vector2()
  skeleton.getBounds(offset, size, [])
  return { offset: offset, size: size }
}

const render = () => {
  const now = Date.now() / 1000
  const delta = now - lastFrameTime
  lastFrameTime = now
  // Update the MVP matrix to adjust for canvas size changes
  gl.clearColor(0,0,0,0)
  gl.clear(gl.COLOR_BUFFER_BIT)
  shader.bind()
  shader.setUniformi(spine.webgl.Shader.SAMPLER, 0)
  shader.setUniform4x4f(spine.webgl.Shader.MVP_MATRIX, mvp.values)
  // Start the batch and tell the SkeletonRenderer to render the active skeleton.
  batcher.begin(shader)

  for (const item of extensionOption.spineList) {
    resize(item)
    // calcSpinePosition(spineItem)
    item.spineboy.skeleton.scaleX = extensionOption.scaleXGlobal
    const skeletonOther = item.spineboy.skeleton
    const stateOther = item.spineboy.state
    const premultipliedAlphaOther = item.spineboy.premultipliedAlpha
  
    stateOther.update(delta)
    stateOther.apply(skeletonOther)
    skeletonOther.updateWorldTransform()
    skeletonRenderer.premultipliedAlpha = premultipliedAlphaOther
    skeletonRenderer.draw(batcher, skeletonOther)
  
  }

  batcher.end()
  shader.unbind()
  animationFrameId = requestAnimationFrame(render)
}

const resize = (spineItem) => {
  const w = canvas.clientWidth
  const h = canvas.clientHeight
  if (canvas.width != w || canvas.height != h) {
    canvas.width = w
    canvas.height = h
  }
  
  // Calculations to center the skeleton in the canvas.
  const bounds = spineItem.spineboy.bounds
  const centerX = bounds.offset.x + bounds.size.x / 2
  const centerY = bounds.offset.y + bounds.size.y / 2
  const scaleX = bounds.size.x / canvas.width
  const scaleY = bounds.size.y / canvas.height
  // 差不多0.1-0.5的区间
  scale = Math.max(scaleX, scaleY) / extensionOption.scaleMult
  // console.log(scale)
  if (scale < 1) scale = 1
  const width = canvas.width * scale
  const height = canvas.height * scale
  calcSpinePosition(spineItem)
  // redirectSpine(spineItem)
  mvp.ortho2d(centerX - width / 2, centerY - height / 2, width, height)

  gl.viewport(0, 0, canvas.width, canvas.height)
}

// task:队列下标，time:延迟时间
const schedule = (ququeIndex, time, amintionRun) => {
  tasks.push({
    delay: time,
    timeout: () => {
      // console.log(`${time} - Task ${ququeIndex} executed`)
      ar = amintionRun
    //   animationFrameId = requestAnimationFrame(move)
    }
  })
}
const runTasks = () => {
    if (!tasks.length) {
        setupAnimations()
        return 
    }
    startFrameTime = Date.now() / 1000
    const task = tasks.shift()
    task?.timeout()
    runTasksTime = setTimeout(runTasks, task.delay*1000)
}

// 计算小人位置
const calcSpinePosition = (spineItem) => {
  const spineMoveX = spineItem.spineboy.skeleton.x/scale
  const spineMoveY = spineItem.spineboy.skeleton.y/scale
  const bonesPositionX = spineItem.spineboy.skeleton.bones.map((p) => p.x)
  const bonesPositionY = spineItem.spineboy.skeleton.bones.map((p) => p.y)
  bonesPositionXMax = Math.max(...bonesPositionX)/scale
  bonesPositionYMax = (Math.max(...bonesPositionY) + Math.abs(Math.min(...bonesPositionY)))/scale
  spinePositionEl.style.height = bonesPositionYMax+'px'
  spinePositionEl.style.width = bonesPositionXMax+'px'
  const w = canvas.clientWidth
  const h = canvas.clientHeight
  bonesTop = h/2 - bonesPositionYMax/2 - spineMoveY
  bonesLeft = w/2 - bonesPositionXMax/2 + spineMoveX
  spinePositionEl.style.top = bonesTop+'px'
  spinePositionEl.style.left = bonesLeft+'px'
  // 设置位置模式，两种方案：1、给一个开关。2、移动时就自动记录坐标
  // 根据开关来开启此功能
  // if (isSetting) {
  //   settingOption = {
  //     position: {
  //       y: spineItem.spineboy.skeleton.y,
  //       x: spineItem.spineboy.skeleton.x,
  //     },
  //     isIndex: spineItem.isIndex,
  //     name: spineItem.name,
  //     key: spineItem.key,
  //     skelPath: spineItem.skelPath,
  //     atlasPath: spineItem.atlasPath
  //   }
  // }
  // 自动记录坐标
  settingOption = {
    position: {
      y: spineItem.spineboy.skeleton.y,
      x: spineItem.spineboy.skeleton.x,
    },
    isIndex: spineItem.isIndex,
    name: spineItem.name,
    key: spineItem.key,
    skelPath: spineItem.skelPath,
    atlasPath: spineItem.atlasPath
  }
}

const redirectSpine = (spineItem) => {
  
  const w = canvas.clientWidth
  const h = canvas.clientHeight
  const position = spineItem.position
  spineItem.spineboy.skeleton.x = position?.x
  spineItem.spineboy.skeleton.y = position?.y
  resize(spineItem)
}

// 触摸反馈
o_spine_position.onclick = () => {
  tasks = []
  // cancelAnimationFrame(animationFrameId)
  clearTimeout(runTasksTime)
  ququeIndex = 1
  setupAnimations(1)
}

// 拖拽小人实现移动
o_spine_position.onmousedown = (e) => {
  document.onselectstart = function() { return false }
  let initX = e.clientX
  let initY = e.clientY 
  spinePositionEl.onmousemove = (e) => {
    document.onselectstart = function() { return false }
    if (spinePositionEl.style.left <= 0 || 
      spinePositionEl.style.top <= 0 || 
      spinePositionEl.style.left >= canvas.clientWidth - spinePositionEl.style.width || 
      spinePositionEl.style.top >= canvas.clientHeight - spinePositionEl.style.height) {
        spinePositionEl.onmousemove = null
        spinePositionEl.onmouseup = null
      return
    }
    let x = e.clientX - initX
    let y = e.clientY - initY
    initX = e.clientX
    initY = e.clientY 
    
    
    spineboy.skeleton.x = spineboy.skeleton.x+x*scale
    spineboy.skeleton.y = spineboy.skeleton.y-y*scale
    // 判断是否到达边界，问题在于是否有添加次功能的必要呢，由于定位框是移动的，
    // 不能流畅地贴合边界会导致一些交互的问题，目前没有好的方法解决此问题，
    // 而没有此功能似乎也不影响交互，决定不采用
    
  }
  spinePositionEl.onmouseup = () => {
    // let selectSpine = spinesData.popupSpineList.find(p => p.key === settingOption.key)
    // settingOption.skelPath = selectSpine?.skelPath
    // settingOption.atlasPath = selectSpine?.atlasPath
    // spinesData.indexSpineList = [settingOption]
    // chrome.storage.sync.set({'IndexSpine': JSON.stringify(spinesData)}, function() {
    // });
    // settingOption = null
    window.dispatchEvent(new CustomEvent('settingPositionEvent'));
    spinePositionEl.onmousemove = null
    spinePositionEl.onmouseup = null
  }
}

const changeDirection = (flag) => {
  extensionOption.scaleXGlobal = flag
}

const settingPosition = (flag) => {
  isSetting = flag
}

const changeScale = (value) => {
  extensionOption.scaleMult = value
}

const destroy = () => {
  cancelAnimationFrame(animationFrameId)
  clearTimeout(runTasksTime)
  mvp = new spine.webgl.Matrix4()
  scale = 1
  assetManager = null
}

const Ospine = {
  injectOption,
  init,
  changeDirection,
  settingPosition,
  changeScale,
  destroy
}